package io.netty.test.main.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;

import java.util.Date;

/**
 * @author LuoYu
 * @date 2018/11/02 20:22
 * <p>
 * Description:
 * 接收/处理/响应客户端websocket请求的核心业务处理类
 */
public class BizHandler extends SimpleChannelInboundHandler<Object> {

    private WebSocketServerHandshaker handshaker;

    private static final String WEB_SOCKET_URL = "ws://localhost:8888/websocket";

    /**
     * 当客户端与服务端创建连接时调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        NettyConfig.group.add(ctx.channel());
        System.out.println("客户端连接开启");
    }

    /**
     * 当客户端与服务端断开连接时调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        NettyConfig.group.remove(ctx.channel());
        System.out.println("客户端连接断开");
    }

    /**
     * 服务端接收客户端发送数据接收完毕之后调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    /**
     * 出现异常时调用
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.flush();
    }

    /**
     * 处理请求的核心方法
     * 处理请求并返回响应 作者在javadoc中表示messageReceived名字更适合这个方法，但netty5.0流产了
     * 上面的几个继承自{@link io.netty.channel.ChannelInboundHandlerAdapter} 类似于spring中的aop
     * @param ctx           the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}
     *                      belongs to 提供当前handler所在的上下文
     * @param msg           the message to handle
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {

        if (msg instanceof FullHttpRequest){
            // 客户端用来发起webSocket连接的http握手请求
            handlerHttpRequest(ctx, (FullHttpRequest) msg);
        } else if(msg instanceof WebSocketFrame){
            // 处理webSocket连接业务
            handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
        }

        // 构造简单的http协议的响应
//        ByteBuf content = Unpooled.copiedBuffer("luoyu666",CharsetUtil.UTF_8);
//        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
//                HttpResponseStatus.OK,content);
//        response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
//        response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
//        ctx.writeAndFlush(response);
    }

    private void handlerWebSocketFrame(ChannelHandlerContext ctx,WebSocketFrame frame){
        // 处理特殊的webSocket指令
        if (frame instanceof CloseWebSocketFrame){
            // 关闭webSocket的指令
            handshaker.close(ctx.channel(),((CloseWebSocketFrame) frame).retain());
        } else if (frame instanceof PingWebSocketFrame) {
            // ping指令
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        } else if (!(frame instanceof TextWebSocketFrame)){
            // 非文本指令
            System.out.println("不支持二进制消息");
            throw new RuntimeException("【"+this.getClass().getName()+"】不支持消息");
        }

        // 处理客户端消息
        // 读取
        String request = ((TextWebSocketFrame)frame).text();
        System.out.println("服务端收到来自客户端的消息》》》"+request);

        // 应答
        TextWebSocketFrame webSocketFrame = new TextWebSocketFrame(new Date().toString() + ctx.channel().id() +
                                            "》》》" + request);

        // 连接广播 可以实现聊天室功能
        NettyConfig.group.writeAndFlush(webSocketFrame);

    }

    /**
     * 处理客户端向服务端发起http捂手请求
     * @param ctx
     * @param request
     */
    private void handlerHttpRequest(ChannelHandlerContext ctx,FullHttpRequest request){
        if (!request.getDecoderResult().isSuccess() ||
                !("websocket".equals(request.headers().get("Upgrade")))){
            sendHttpResponse(ctx,request,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.BAD_REQUEST));
            return;
        }
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(WEB_SOCKET_URL,null,false);
        handshaker = wsFactory.newHandshaker(request);
        if (handshaker == null){
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(),request);
        }
    }

    /**
     * 响应客户端消息
     * @param ctx
     * @param request
     * @param response
     */
    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request,
                                  DefaultFullHttpResponse response){
        if (response.getStatus().code() != 200){
            // 请求失败
            ByteBuf buf = Unpooled.copiedBuffer(response.getStatus().toString(),
                    CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
        }
        // 服务端向客户端发送数据
        ChannelFuture future = ctx.channel().writeAndFlush(response);
        if (response.getStatus().code() != 200){
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }
}
